import { ConfigPreview } from "@/docs/components/Preview";
import { AutoField, FieldLabel } from "@/puck";

# Custom Fields

Puck can be extended with completely custom fields for different use-cases.

## Creating a custom field

Creating a custom field is possible using the [`custom` field type](/docs/api-reference/fields/custom):

```tsx copy showLineNumbers {5-15}
const config = {
  components: {
    Example: {
      fields: {
        title: {
          type: "custom",
          render: ({ name, onChange, value }) => (
            <input
              defaultValue={value}
              name={name}
              onChange={(e) => onChange(e.currentTarget.value)}
              style={{ border: "1px solid black", padding: 4 }}
            />
          ),
        },
      },
      render: ({ title }) => {
        return <p>{title}</p>;
      },
    },
  },
};
```

The [`onChange` function](/docs/api-reference/fields/custom#onchangevalue-ui) updates the Puck data payload for the field name, in this case "title".

<ConfigPreview
  label="Example"
  componentConfig={{
    fields: {
      title: {
        type: "custom",
        render: ({ name, onChange, value }) => {
          return (
            <input
              defaultValue={value}
              name={name}
              onChange={(e) => onChange(e.currentTarget.value)}
              style={{
                background: "white",
                border: "1px solid black",
                padding: 4,
              }}
            />
          );
        },
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    render: ({ title }) => {
      return <p style={{ margin: 0 }}>{title}</p>;
    },
  }}
/>

## Adding a label

You can add your own label, but it's recommended to use the [`<FieldLabel>` component](/docs/api-reference/components/field-label) provided by Puck to seamlessly integrate into the Puck field UI.

```tsx copy showLineNumbers {1, 11-13}
import { FieldLabel } from "@measured/puck";

const config = {
  components: {
    Example: {
      fields: {
        title: {
          type: "custom",
          label: "Label Example",
          render: ({ field }) => (
            <FieldLabel label={field.label}>
              <input {/*...*/} />
            </FieldLabel>
          ),
        },
      },
      // ...
    },
  },
};
```

<ConfigPreview
  label="Example"
  componentConfig={{
    fields: {
      title: {
        type: "custom",
        label: "Label Example",
        render: ({ field, name, onChange, value }) => {
          return (
            <FieldLabel label={field.label}>
              <input
                defaultValue={value}
                name={name}
                onChange={(e) => onChange(e.currentTarget.value)}
                style={{
                  background: "white",
                  border: "1px solid black",
                  padding: 4,
                }}
              />
            </FieldLabel>
          );
        },
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    render: ({ title }) => {
      return <p style={{ margin: 0 }}>{title}</p>;
    },
  }}
/>

## Rendering Puck fields internally

Use the [`<AutoField>` component](/docs/api-reference/components/auto-field) to render Puck fields within your custom field.

```tsx copy showLineNumbers {1, 12-16}
import { AutoField } from "@measured/puck";

const config = {
  components: {
    Example: {
      fields: {
        title: {
          type: "custom",
          label: "Label Example",
          render: ({ field, value, onChange }) => (
            <FieldLabel label={field.label}>
              <AutoField
                field={{ type: "text" }}
                onChange={(value) => onChange(value)}
                value={value}
              />
            </FieldLabel>
          ),
        },
        // ...
      },
    },
  },
};
```

<ConfigPreview
  label="Example"
  componentConfig={{
    fields: {
      title: {
        type: "custom",
        label: "AutoField Example",
        render: ({ field, value, onChange }) => {
          return (
            <FieldLabel label={field.label}>
              <AutoField
                field={{ type: "text" }}
                value={value}
                onChange={onChange}
              />
            </FieldLabel>
          );
        },
      },
    },
    defaultProps: {
      title: "Hello, world",
    },
    render: ({ title }) => {
      return <p style={{ margin: 0 }}>{title}</p>;
    },
  }}
/>

## Updating the UI state

The [`onChange` function](/docs/api-reference/fields/custom#onchangevalue-ui) can also be used to modify the [Puck UI state](/docs/api-reference/data-model/app-state#ui) at the same time as updating the field value:

```tsx copy showLineNumbers {14,15}
const config = {
  components: {
    Example: {
      fields: {
        title: {
          type: "custom",
          render: ({ name, onChange, value }) => (
            <input
              defaultValue={value}
              name={name}
              onChange={(e) =>
                onChange(
                  e.currentTarget.value,
                  // Close the left side bar when this field is changed
                  { leftSideBarVisible: false }
                )
              }
              style={{ border: "1px solid black", padding: 4 }}
            />
          ),
        },
      },
      render: ({ title }) => {
        return <p>{title}</p>;
      },
    },
  },
};
```

## Further reading

- [The `<AutoField>` API reference](/docs/api-reference/components/auto-field)
- [The `<FieldLabel>` API reference](/docs/api-reference/components/field-label)
- [The `custom` field API reference](/docs/api-reference/fields/custom)
